<template>
  <div class="global-uploader">
    <!-- 上传 -->
    <uploader ref="uploader" :options="initOptions" :fileStatusText="fileStatusText" :autoStart="false"
              class="uploader-app" @file-added="onFileAdded" @file-success="onFileSuccess" @file-progress="onFileProgress"
              @file-error="onFileError"
    >
      <uploader-unsupport></uploader-unsupport>

      <uploader-btn ref="uploadBtn" class="global-uploader-btn" :class="`${name}-uploader-btn`">选择文件</uploader-btn>

      <!-- 附件进度 -->
      <el-dialog title="附件进度" :visible.sync="filesProcessDialog" class="filesProcessDialog" :close-on-click-modal="false"
                 :close-on-press-escape="false" :show-close="false" width="500px" :modal="false"
      >
        <div>
          <uploader-list v-show="filesProcessDialog">
            <div slot-scope="props" class="file-panel" :class="{ collapse: collapse }">
              <ul class="file-list">
                <li v-for="file in props.fileList" :key="file.id" class="file-item" :title="file.name">
                  <uploader-file ref="files" :class="['file_' + file.id, customStatus]" :file="file"></uploader-file>
                </li>
                <div v-if="!props.fileList.length" class="no-file">
                  <i class="iconfont icon-empty-file"></i> 暂无待上传文件
                </div>
              </ul>
            </div>
          </uploader-list>
        </div>
        <span slot="footer" class="dialog-footer">
          <el-button size="small" @click="close">关 闭</el-button>
        </span>
      </el-dialog>
    </uploader>
  </div>
</template>

<script>
/**
 *  全局上传插件，两种调用方式
 *   1. 作为全局页面的组件，使用event bus
 *   调用方法：Bus.$emit('openUploader', {params: {}, options: {}})
 *               params: 发送给服务器的额外参数；
 *               options：上传选项，目前支持 target、testChunks、mergeFn、accept
 *
 *   监听函数：Bus.$on('fileAdded', fn); 文件选择后的回调
 *           Bus.$on('fileSuccess', fn); 文件上传成功的回调，监听后记得释放
 *
 *   2. 作为普通组件在单个页面中调用，使用props
 */
import { ACCEPT_CONFIG } from './js/config'
import Bus from './js/bus'
import SparkMD5 from 'spark-md5'
import { methodPostApi } from '@/api/publicApi' // 数字转文字
import { getToken } from '@/utils/publicUtils/auth'
import { toChinesNum } from '@/utils/commonUtils/HLUtils.js'

export default {
  props: {
    global: {
      type: Boolean,
      default: true
    },
    // 发送给服务器的额外参数
    params: {
      type: Object
    },
    options: {
      type: Object
    },
    // 单个固定一个  例如： name = 'name'
    // 如果是表格 多上传文件需要 多个不同name  例如 name = 'name1' 、name ='name2' ... 循环
    name: {
      type: String
    },

    // 上传成功 数量
    successList: {
      type: Array,
      default: () => []
    },
    // 是否多选
    multiple: {
      type: Boolean,
      default: true
    },
    // 可上传的数量
    limit: {
      type: Number
    }
  },

  data() {
    return {
      initOptions: {
        accept: [],
        target: process.env.VUE_APP_BASE_API + '/sys/annex/upload',
        chunkSize: 50 * 1024 * 1024,
        fileParameterName: 'file',
        singleFile: !this.multiple,
        maxChunkRetries: 3,
        // 是否开启服务器分片校验
        testChunks: true,
        headers: {
          'tenant-id': localStorage.getItem('tenantId'),
          'x-auth-token': getToken()
        },
        // 服务器分片校验函数，秒传及断点续传基础
        checkChunkUploadedByResponse: function(chunk, message) {
          const objMessage = JSON.parse(message)
          const dataObj = objMessage.data
          if (dataObj.isUpload) {
            return true
          }
          return (dataObj.chunkNumbers || []).indexOf(chunk.offset + 1) >= 0
        },
        parseTimeRemaining: function(timeRemaining, parsedTimeRemaining) {
          // 格式化时间
          return '剩余时间：' + parsedTimeRemaining
            .replace(/\syears?/, '年')
            .replace(/\days?/, '天')
            .replace(/\shours?/, '小时')
            .replace(/\sminutes?/, '分钟')
            .replace(/\sseconds?/, '秒')
        },
        query: (file, chunk) => {
          return {
            ...file.params
          }
        }
      },
      fileStatusText: {
        success: '上传成功',
        error: '上传失败',
        uploading: '上传中',
        paused: '已暂停',
        waiting: '等待上传'
      },
      filesProcessDialog: false,
      panelShow: false, // 选择文件后，展示上传panel
      collapse: false,
      customParams: {},
      customStatus: ''
    }
  },

  watch: {
    params: {
      handler(data) {
        if (data) {
          this.customParams = data
        }
      },
      immediate: true
    },
    options: {
      handler(data) {
        if (data) {
          setTimeout(() => {
            this.customizeOptions(data)
          }, 0)
        }
      },
      immediate: true
    }
  },

  mounted() {
    // Bus.$on('openUploader', ({ params = {}, options = {}}) => {
    //   this.customParams = params

    //   this.customizeOptions(options)

    //   if (this.$refs.uploadBtn) {
    //     this.$refs.uploadBtn.$el.click()
    //   }
    // })
  },

  computed: {
    // Uploader实例
    uploader() {
      return this.$refs.uploader.uploader
    }
  },

  methods: {
    upload(obj) {
      // 验证上传个数
      var fileList = this.uploader.fileList
      var successList = this.successList
      // 判断是图片 还是 附件
      var fileTypeName = '个附件'
      if(obj.uploadType === 'image'){
        fileTypeName = '张图片'
      }
      // 数量限制
      if (this.multiple == false) {
        if (fileList.length >= this.limit) {
          // console.log('---------fileList----------')
          this.msgAlt(`最多可上传${toChinesNum(this.limit)}${fileTypeName}`,'error')
          return
        }

        if (Number(fileList.length + successList.length) >= this.limit) {
          // console.log('---------successList----------')
          this.msgAlt(`最多可上传${toChinesNum(this.limit)}${fileTypeName}`,'error')
          return
        }
      }
      // /* else {
      //   if(this.limit != 0){
      //     if (fileList.length >= this.limit) {
      //       // console.log('---------fileList----------')
      //       this.msgAlt('最多可上传' + toChinesNum(this.limit) + '个附件','error')
      //       return
      //     }
      //
      //     if (Number(fileList.length + successList.length) >= this.limit) {
      //       // console.log('---------successList----------')
      //       this.msgAlt('最多可上传' + toChinesNum(this.limit) + '个附件','error')
      //       return
      //     }
      //   }
      // }*/
      // 自定义options
      this.customizeOptions(obj)
      // 触发按钮
      if (this.$refs.uploadBtn) {
        this.$refs.uploadBtn.$el.click()
      }
    },

    statusFiles(progress) {
      var type = ''
      if (progress >= 1) {
        type = 'success'
      } else {
        type = 'format'
      }
      return type
    },

    // 自定义options
    customizeOptions(opts) {
      // console.log('-----------111111-=', opts)
      // 自定义上传url
      if (opts.target) {
        this.uploader.opts.target = opts.target
      }
      // 是否可以秒传、断点续传
      if (opts.testChunks !== undefined) {
        this.uploader.opts.testChunks = opts.testChunks
      }
      // // merge 的方法，类型为Function，返回Promise
      // this.mergeFn = opts.mergeFn || mergeSimpleUpload

      // 自定义文件上传类型
      const input = document.querySelector(`.${this.name}-uploader-btn input`)
      const accept = opts.accept || ACCEPT_CONFIG.getAll()
      input.setAttribute('accept', accept.join())
    },

    onFileAdded(file) {
      console.log(file)
      this.emit('fileAdded')
      this.filesProcessDialog = true

      // 将额外的参数赋值到每个文件上，以不同文件使用不同params的需求
      file.params = this.customParams
      // 计算MD5
      this.computeMD5(file).then((result) => this.startUpload(result))
    },

    computeMD5(file) {
      console.log(file)
      this.statusSet(file.id, 'md5')
      file.pause()

      // 单个文件的大小限制2G
      const fileSizeLimit = 10 * 1024 * 1024 * 1024
      // console.log('文件大小：' + file.size)
      // console.log('限制大小：' + fileSizeLimit)
      if (file.size > fileSizeLimit) {
        this.msgAlt('文件大小不能超过2G', 'error')
        file.cancel()
      }

      const fileReader = new FileReader()
      const time = new Date().getTime()
      const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
      let currentChunk = 0
      const chunkSize = 50 * 1024 * 1024
      const chunks = Math.ceil(file.size / chunkSize)
      const spark = new SparkMD5.ArrayBuffer()
      // 对文件名称金额ArrayBuffer
      spark.append(this.stringToArrayBuffer(file.name))
      // 由于计算整个文件的Md5太慢，因此采用只计算第1块文件的md5的方式
      const chunkNumberMD5 = 1

      loadNext()
      return new Promise((resolve, reject) => {
        fileReader.onload = e => {
          spark.append(e.target.result)
          if (currentChunk < chunkNumberMD5) {
            loadNext()
          } else {
            // spark.append(e.target.result)
            const md5 = spark.end()
            // md5计算完毕
            resolve({ md5, file })
            // file.uniqueIdentifier = md5
            // file.resume()
            // console.log(`MD5计算完毕：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${file.size} 用时：${new Date().getTime() - time} ms`)
          }
        }

        fileReader.onerror = function() {
          this.error(`文件${file.name}读取出错，请检查该文件`)
          file.cancel()
          reject()
        }
      })

      function loadNext() {
        const start = currentChunk * chunkSize
        const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
        currentChunk++
        // console.log('计算第' + currentChunk + '块')
      }
    },

    stringToArrayBuffer(str) {
      const encoder = new TextEncoder() // 创建TextEncoder实例
      const encodedArray = encoder.encode(str) // 将字符串转换为UTF-8编码的Uint8Array
      const buffer = encodedArray.buffer // 获取ArrayBuffer
      return buffer
    },

    /**
     * 计算md5值，以实现断点续传及秒传
     * @param file
     * @returns Promise
     */
    // computeMD5(file) {
    //   const fileReader = new FileReader()
    //   const time = new Date().getTime()
    //   const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
    //   let currentChunk = 0
    //   const chunkSize = 10 * 1024 * 1000
    //   const chunks = Math.ceil(file.size / chunkSize)
    //   const spark = new SparkMD5.ArrayBuffer()
    //
    //   // 文件状态设为"计算MD5"
    //   this.statusSet(file.id, 'md5')
    //   file.pause()
    //
    //   loadNext()
    //
    //   return new Promise((resolve, reject) => {
    //     fileReader.onload = (e) => {
    //       spark.append(e.target.result)
    //
    //       if (currentChunk < chunks) {
    //         currentChunk++
    //         loadNext()
    //
    //         // 实时展示MD5的计算进度
    //         this.$nextTick(() => {
    //           const md5ProgressText = '校验MD5 ' + ((currentChunk / chunks) * 100).toFixed(0) + '%'
    //           document.querySelector(`.custom-status-${file.id}`).innerText = md5ProgressText
    //         })
    //       } else {
    //         const md5 = spark.end()
    //
    //         // md5计算完毕
    //         resolve({ md5, file })
    //
    //         console.log(
    //           `MD5计算完毕：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${file.size} 用时：${
    //             new Date().getTime() - time
    //           } ms`
    //         )
    //       }
    //     }
    //
    //     fileReader.onerror = function() {
    //       this.error(`文件${file.name}读取出错，请检查该文件`)
    //       file.cancel()
    //       reject()
    //     }
    //   })
    //
    //   function loadNext() {
    //     const start = currentChunk * chunkSize
    //     const end = start + chunkSize >= file.size ? file.size : start + chunkSize
    //
    //     fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
    //   }
    // },

    // md5计算完毕，开始上传
    startUpload({ md5, file }) {
      file.uniqueIdentifier = md5
      file.resume()
      this.statusRemove(file.id)
    },

    onFileSuccess(rootFile, file, response, chunk) {
      console.log(2222)
      // const res = JSON.parse(response)

      // 服务端自定义的错误（即http状态码为200，但是是错误的情况），这种错误是Uploader无法拦截的
      // if (!res.result) {
      //   this.error(res.message)
      //   // 文件状态设为“失败”
      //   this.statusSet(file.id, 'failed')
      //   return
      // }

      this.statusSet(file.id, 'merging')
      const params = {
        'fileSize': file.size,
        'fileType': file.fileType,
        'fileName': file.name,
        'identifier': file.uniqueIdentifier
      }
      methodPostApi('/sys/annex/mergeFile', params).then(res => {
        this.$emit('fileSuccess', res.data)
        setTimeout(() => {
          this.close()
        }, 500)
        this.statusRemove(file.id)
      }).catch(() => { // error
        // console.log('合并后捕获的未知异常：' + error)
      })
      // filesMergeAll(params).then(res => {
      //   this.$emit('fileSuccess', res.data)
      //   setTimeout(() => {
      //     this.close()
      //   }, 500)
      //   this.statusRemove(file.id)
      // }).catch(() => { // error
      //   // console.log('合并后捕获的未知异常：' + error)
      // })

      // 如果服务端返回了需要合并的参数
      // if (res.needMerge) {
      //   // 文件状态设为“合并中”
      //   this.statusSet(file.id, 'merging')
      //
      //   this.mergeFn({
      //     tempName: res.tempName,
      //     fileName: file.name,
      //     ...file.params
      //   })
      //       .then((res) => {
      //         // 文件合并成功
      //         this.emit('fileSuccess')
      //
      //         this.statusRemove(file.id)
      //       })
      //       .catch((e) => {
      //       })
      //
      //   // 不需要合并
      // } else {
      //   this.emit('fileSuccess')
      //   console.log('上传成功')
      // }
    },

    onFileProgress(rootFile, file, chunk) {
      // console.log(
      //   `上传中 ${file.name}，chunk：${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024
      //   }`
      // )
    },

    onFileError(rootFile, file, response, chunk) {
      this.error(response)
    },

    close() {
      this.uploader.cancel()
      this.filesProcessDialog = false
    },

    /**
     * 新增的自定义的状态: 'md5'、'merging'、'transcoding'、'failed'
     * @param id
     * @param status
     */
    statusSet(id, status) {
      const statusMap = {
        md5: {
          text: '校验MD5',
          bgc: '#fff'
        },
        merging: {
          text: '合并中',
          bgc: '#e2eeff'
        },
        transcoding: {
          text: '转码中',
          bgc: '#e2eeff'
        },
        failed: {
          text: '上传失败',
          bgc: '#e2eeff'
        }
      }

      this.customStatus = status
      this.$nextTick(() => {
        const statusTag = document.createElement('p')
        statusTag.className = `custom-status-${id} custom-status`
        statusTag.innerText = statusMap[status].text
        statusTag.style.backgroundColor = statusMap[status].bgc

        const statusWrap = document.querySelector(`.file_${id} .uploader-file-status`)
        statusWrap.appendChild(statusTag)
      })
    },

    statusRemove(id) {
      this.customStatus = ''
      this.$nextTick(() => {
        const statusTag = document.querySelector(`.custom-status-${id}`)
        statusTag.remove()
      })
    },

    emit(e) {
      Bus.$emit(e)
      this.$emit(e)
    },

    error(msg) {
      this.$notify({
        title: '错误',
        message: msg,
        type: 'error',
        duration: 2000
      })
    }
  }
}
</script>

<style lang="scss" scoped>
    .global-uploader {
        &:not(.global-uploader-single) {
            position: fixed;
            z-index: 20;
            right: 15px;
            bottom: 15px;
            box-sizing: border-box;
        }

        .uploader-app {
            width: 800px;
            font-size: 12px;
        }

        .file-panel {
            /* background-color: #fff; */
            /* border: 1px solid #e2e2e2; */
            /* border-radius: 7px 7px 0 0; */
            /* box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); */
            .file-list {
                position: relative;
                height: 240px;
                overflow-x: hidden;
                overflow-y: auto;
                background-color: #fff;
                transition: all 0.3s;

                .file-item {
                    background-color: #fff;
                }

                .uploader-file-meta {
                    width: 0;
                }

                .uploader-file-status {
                    width: 32%;
                    text-indent: 20px;
                }
            }

            &.collapse {
                .file-title {
                    background-color: #e7ecf2;
                }

                .file-list {
                    height: 0;
                }
            }
        }

        .no-file {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            font-size: 16px;
        }

        .uploader-file {
            border: none !important;
            &.md5 {
                .uploader-file-resume {
                    display: none;
                }
            }
        }

        .uploader-file-icon {
          &:before {
            content: '' !important;
          }

          &[icon='image'] {
            background: url(./images/image-icon.png);
          }

          &[icon=audio] {
            background: url(./images/audio-icon.png);
            background-size: contain;
          }

          &[icon='video'] {
            background: url(./images/video-icon.png);
          }

          &[icon='document'] {
            background: url(./images/text-icon.png);
          }

          &[icon=unknown] {
            background: url(./images/zip.png) no-repeat center;
            background-size: contain;
          }
        }

        .uploader-file-actions>span {
            margin-right: 6px;
        }

        .custom-status {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            z-index: 1;
        }

        /* 隐藏上传按钮 */
        .global-uploader-btn {
            position: absolute;
            clip: rect(0, 0, 0, 0);
        }
    }
</style>
